// Copyright (c) 2025 Shenzhen Kaihong Digital Industry Development Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::{path::{Path, PathBuf}, task::Poll};

use futures_util::{Stream, StreamExt};
use tokio::fs::ReadDir;

use ohmw_codegen::msg_def::{MsgDefMap, MsgDefParseError};

#[derive(Debug, thiserror::Error)]
pub enum ScanMsgDefsError {
    #[error(transparent)]
    Io(#[from] std::io::Error),
    #[error("failed to parse message definition '{1}'")]
    ParseFailed(#[source] MsgDefParseError, PathBuf),
}

pub struct FindMsgFiles {
    read_dir: ReadDir,
}

pub fn get_search_paths() -> Vec<PathBuf> {
    roslibrust_rospack::get_search_paths()
}

pub fn get_package_paths(search_paths: Vec<PathBuf>) -> Vec<roslibrust_rospack::Package> {
    roslibrust_rospack::crawl(search_paths)
}

pub async fn find_msg_files<P: AsRef<Path>>(path: P) -> Result<FindMsgFiles, std::io::Error> {
    Ok(FindMsgFiles {
        read_dir: tokio::fs::read_dir(path).await?,
    })
}

pub async fn scan_msg_defs(search_paths: Vec<PathBuf>) -> Result<MsgDefMap, ScanMsgDefsError> {
    let packages = get_package_paths(search_paths);
    let mut msg_defs = MsgDefMap::default();
    for package in packages {
        let msg_dir = package.path.join("msg");
        let Ok(mut find_msg_files) = find_msg_files(&msg_dir).await else {
            continue;
        };
        while let Some(path) = find_msg_files.next().await {
            let path = path?;
            if let Some(msg_name) = path.file_stem() {
                let message_string = tokio::fs::read_to_string(&path).await?;
                msg_defs.try_add_ros_msg_def(
                    package.name.to_string(),
                    msg_name.to_string_lossy().into_owned(),
                    &message_string,
                ).map_err(|e| ScanMsgDefsError::ParseFailed(e, path.clone()))?;
            }
        }
    }
    Ok(msg_defs)
}

impl Stream for FindMsgFiles {
    type Item = std::io::Result<PathBuf>;

    fn poll_next(
        mut self: std::pin::Pin<&mut Self>,
        cx: &mut std::task::Context,
    ) -> std::task::Poll<Option<Self::Item>> {
        let entry = self.read_dir.poll_next_entry(cx)?;
        match entry {
            Poll::Ready(Some(entry)) => {
                let path = entry.path();
                if path.is_file() && path.extension().map(|ext| ext == "msg").unwrap_or(false) {
                    return std::task::Poll::Ready(Some(Ok(path)));
                }
                self.poll_next(cx)
            }
            Poll::Ready(None) => Poll::Ready(None),
            Poll::Pending => Poll::Pending,
        }
    }
}
